#include <linux/ktime.h>
#include <linux/clocksource.h>
#include <linux/seqlock.h>
#include <linux/timekeeping.h>
#include <linux/math.h>
#include <linux/stop_machine.h>

#define TIMEKEEPERS_MAX 1
#define TIMEKEEPER_CORE 0

struct tk_read_base
{
    struct clocksource *clock;
    u32 mult;
    u32 shift;
    u64 xtime_nsec;
    ktime_t base;
    u64 base_real;
    u64 mask;
    u64 cycle_last;
};

struct timekeeper
{
    struct tk_read_base tkr_mono;

    union
    {
        ktime_t offsets[TK_OFFS_MAX];

        struct
        {
            ktime_t offs_real;
            ktime_t offs_boot;
            union
            {
                ktime_t offs_tai;
                ktime_t offs_aux;
            };
        };
    };
};

/*
 * The most important data for readout fits into a single 64 byte
 * cache line.
 */
struct tk_data
{
    seqcount_raw_spinlock_t seq;
    struct timekeeper timekeeper;
    struct timekeeper shadow_timekeeper;
    raw_spinlock_t lock;
};

static struct tk_data timekeeper_data[TIMEKEEPERS_MAX];

/* The core timekeeper */
#define tk_core (timekeeper_data[TIMEKEEPER_CORE])

static inline u64 tk_clock_read(const struct tk_read_base *tkr)
{
    struct clocksource *clock = READ_ONCE(tkr->clock);

    return clock->read(clock);
}

static u64 delta_to_ns_safe(const struct tk_read_base *tkr, u64 delta)
{
    return mul_u64_u32_add_u64_shr(delta, tkr->mult, tkr->xtime_nsec, tkr->shift);
}

static inline u64 timekeeping_cycles_to_ns(const struct tk_read_base *tkr, u64 cycles)
{
    /* Calculate the delta since the last update_wall_time() */
    u64 mask = tkr->mask, delta = (cycles - tkr->cycle_last) & mask;

    /*
     * This detects both negative motion and the case where the delta
     * overflows the multiplication with tkr->mult.
     */
    if (unlikely(delta > tkr->clock->max_cycles))
    {
        /*
         * Handle clocksource inconsistency between CPUs to prevent
         * time from going backwards by checking for the MSB of the
         * mask being set in the delta.
         */
        if (delta & ~(mask >> 1))
            return tkr->xtime_nsec >> tkr->shift;

        return delta_to_ns_safe(tkr, delta);
    }

    return ((delta * tkr->mult) + tkr->xtime_nsec) >> tkr->shift;
}

static inline u64 timekeeping_get_ns(const struct tk_read_base *tkr)
{
    return timekeeping_cycles_to_ns(tkr, tk_clock_read(tkr));
}

static void tk_read_base_setup(struct tk_read_base *tkr, struct clocksource *clock)
{
    tkr->clock = clock;
    tkr->mask = clock->mask;
    tkr->cycle_last = tk_clock_read(tkr);
    tkr->shift = clock->shift;
    tkr->mult = clock->mult;
}

static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
{
    tk_read_base_setup(&tk->tkr_mono, clock);
}

/*
 * change_clocksource - Swaps clocksources if a new one is available
 *
 * Accumulates current time interval and initializes new clocksource
 */
static int change_clocksource(void *data)
{
    struct clocksource *new = data, *old = NULL;

    { // todo
        struct timekeeper *tks = &tk_core.timekeeper;

        tk_setup_internals(tks, new);
    }

    return 0;
}

/**
 * timekeeping_notify - Install a new clock source
 * @clock:		pointer to the clock source
 *
 * This function is called from clocksource.c after a new, better clock
 * source has been registered. The caller holds the clocksource_mutex.
 */
int timekeeping_notify(struct clocksource *clock)
{
    struct timekeeper *tk = &tk_core.timekeeper;

    if (tk->tkr_mono.clock == clock)
        return 0;

    stop_machine(change_clocksource, clock, NULL);

    return tk->tkr_mono.clock == clock ? 0 : -1;
}

/**
 * ktime_get_update_offsets_now - hrtimer helper
 * @cwsseq:	pointer to check and store the clock was set sequence number
 * @offs_real:	pointer to storage for monotonic -> realtime offset
 * @offs_boot:	pointer to storage for monotonic -> boottime offset
 * @offs_tai:	pointer to storage for monotonic -> clock tai offset
 *
 * Returns current monotonic time and updates the offsets if the
 * sequence number in @cwsseq and timekeeper.clock_was_set_seq are
 * different.
 *
 * Called from hrtimer_interrupt() or retrigger_next_event()
 */
ktime_t ktime_get_update_offsets_now(unsigned int *cwsseq, ktime_t *offs_real,
                                     ktime_t *offs_boot, ktime_t *offs_tai)
{
    struct timekeeper *tk = &tk_core.timekeeper;
    ktime_t base;
    u64 nsecs;
    unsigned int seq;

    // TODO
    base = tk->tkr_mono.base;
    nsecs = timekeeping_get_ns(&tk->tkr_mono);
    base = ktime_add_ns(base, nsecs);

    return base;
}

void timekeeping_init(void)
{
    struct clocksource *clock;

    clock = clocksource_default_clock();

    tk_setup_internals(&tk_core.timekeeper, clock);
}

ktime_t ktime_get_with_offset(enum tk_offsets offs)
{
    struct timekeeper *tk = &tk_core.timekeeper;
    unsigned int seq;
    ktime_t base, *offset = &tk->offsets[offs];
    u64 nsecs;

    do
    {
        seq = read_seqcount_begin(&tk_core.seq);
        base = ktime_add(tk->tkr_mono.base, *offset);
        nsecs = timekeeping_get_ns(&tk->tkr_mono);

    } while (read_seqcount_retry(&tk_core.seq, seq));

    return ktime_add_ns(base, nsecs);
}

ktime_t ktime_get(void)
{
    struct timekeeper *tk = &tk_core.timekeeper;
    ktime_t base;
    u64 nsecs;

    base = tk->tkr_mono.base;
    nsecs = timekeeping_get_ns(&tk->tkr_mono);

    return ktime_add_ns(base, nsecs);
}

ktime_t ktime_get_real(void)
{
    return ktime_get_with_offset(TK_OFFS_REAL);
}

ktime_t ktime_get_boottime(void)
{
    return ktime_get_with_offset(TK_OFFS_BOOT);
}

ktime_t ktime_get_clocktai(void)
{
    return ktime_get_with_offset(TK_OFFS_TAI);
}

/**
 * timekeeping_valid_for_hres - Check if timekeeping is suitable for hres
 */
bool timekeeping_valid_for_hres(void)
{
    struct timekeeper *tk = &tk_core.timekeeper;
    bool ret;

    ret = (tk->tkr_mono.clock->flags & CLOCK_SOURCE_VALID_FOR_HRES) ? true : false;

    return ret;
}
